using System;
using System.Collections.Generic;
using System.Linq;
using PropertyExpression.Common;

namespace PropertyExpression.ReactiveRelation
{
    public static partial class Value
    {
        public static IValue<TResult> Func<T, TArgument, TResult>(
            this IValue<T> value, 
            Func<IArgumentProvider, TArgument> argumentFunc,
            Func<T, TArgument, TResult> func)
        {
            return new FuncValue<T, TArgument, TResult>(value, argumentFunc, func);
        }

        private class FuncValue<T, TArgument, TResult> : IValue<TResult>
        {
            private readonly IValue<T> value;
            private readonly Func<IArgumentProvider, TArgument> argumentFunc;
            private readonly Func<T, TArgument, TResult> func;

            public FuncValue(IValue<T> value, Func<IArgumentProvider, TArgument> argumentFunc,
                Func<T, TArgument, TResult> func)
            {
                this.value = value;
                this.argumentFunc = argumentFunc;
                this.func = func;
            }

            public IEnumerable<ICell> GetCells()
            {
                return Execute(
                    (values, argument) =>
                    values.Select(valueContainer => valueContainer.Value)
                        .Concat(new[] {value}).SelectMany(currentValue => currentValue.GetCells())
                    );
            }

            public IOptionalResult<TResult> GetValue(IPropertySettor propertySettor, 
                IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
            {
                return Execute(
                    (values, argument) =>
                    value.GetValue(propertySettor, oneToManyCollectionNewItemProcessor)
                        .Execute(
                        result =>
                            {
                                var allValuesExist = true;
                                foreach (var valueContainer in values)
                                {
                                    if (valueContainer.InvokeGetValue(
                                            propertySettor, oneToManyCollectionNewItemProcessor) == false)
                                    {
                                        allValuesExist = false;
                                        break;
                                    }
                                }
                                if (allValuesExist)
                                {
                                    return func(result, argument).AsOptionalResult();
                                }
                                else
                                {
                                    return OptionalResult.AsOptionalResult<TResult>();
                                }
                            }
                        )
                    );
            }

            private TExecuteResult Execute<TExecuteResult>(
                Func<IEnumerable<IValueContainer>, TArgument, TExecuteResult> executeFunc)
            {
                var values = new LinkedList<IValueContainer>();
                return executeFunc(values, argumentFunc(new ArgumentProvider(values)));
            }

            private interface IValueContainer
            {
                IValue Value { get; }
                bool InvokeGetValue(IPropertySettor propertySettor, 
                    IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor);
            }

            private class ArgumentProvider : IArgumentProvider
            {
                private readonly LinkedList<IValueContainer> values;

                public ArgumentProvider(LinkedList<IValueContainer> values)
                {
                    this.values = values;
                }

                public Func<TCurrentArgument> _<TCurrentArgument>(IValue<TCurrentArgument> value)
                {
                    var valueContainer = new ValueContainer<TCurrentArgument>(value);
                    values.AddLast(valueContainer);
                    return () => valueContainer.Result();
                }

                private class ValueContainer<TCurrentArgument> : IValueContainer
                {
                    private readonly IValue<TCurrentArgument> value;

                    public ValueContainer(IValue<TCurrentArgument> value)
                    {
                        this.value = value;
                    }

                    public Func<TCurrentArgument> Result { get; private set; }

                    public bool InvokeGetValue(IPropertySettor propertySettor, 
                        IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
                    {
                        var execute = value.GetValue(propertySettor, oneToManyCollectionNewItemProcessor)
                            .Execute(
                            result => new
                                          {
                                              ValueResult = (Func<TCurrentArgument>) (() => result),
                                              MethodResult = true
                                          },
                            () => new
                                      {
                                          ValueResult = (Func<TCurrentArgument>)
                                                        (() => { throw new ApplicationException(); }),
                                          MethodResult = false
                                      }
                            );
                        Result = execute.ValueResult;
                        return execute.MethodResult;
                    }

                    public IValue Value
                    {
                        get { return value; }
                    }
                }
            }
        }
    }
}
